//
//  parser.cc
//  iphttpd
//
//  Created by Zhibiao Pan on 11-11-12.
//  Copyright 2011年 PanZhibiao. All rights reserved.
//
//  QQwry.data format
//  @<see> http://lumaqq.linuxsir.org/article/qqwry_format_detail.html
//

#include "parser.h"

#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>

#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h>

namespace iphttpd {
  
  bool Ip2Str(char *buf, unsigned int buf_len, unsigned int ip) {
    snprintf(buf, buf_len, "%d.%d.%d.%d",
             (ip >> 24) & 0xFF,
             (ip >> 16) & 0xFF,
             (ip >> 8)  & 0xFF,
             ip         & 0xFF);
    return true;
  }
  
  
  CZParser::CZParser():fd_(-1) {}
  
  CZParser::~CZParser() {
    if (fd_ != -1) { close(fd_); }
  }
  
  // 初始化
  // 成功返回0，失败返回-1
  int CZParser::Init(const char *file) {
    struct stat stFileInfo;
    db_size_ = 0;
    data_begin_prt_ = NULL;
    
    fd_ = open(file, O_RDONLY);
    if (fd_ == -1) { return -1; }
    fstat(fd_, &stFileInfo);
    db_size_ = (unsigned int)stFileInfo.st_size;
    data_begin_prt_ = (unsigned char*)mmap(NULL, db_size_, PROT_READ,
                                           MAP_SHARED, fd_, 0);
    if (data_begin_prt_ == NULL) { return -1; }
    
    unsigned char *p = data_begin_prt_;
    first_ip_    = Read4Byte(&p);
    last_ip_     = Read4Byte(&p);
    total_count_ = (last_ip_ - first_ip_) / 7;
    
    return total_count_ ? 0 : -1;
  }
  
  void CZParser::GetArea(unsigned char **p, const char **buf) {
    char chr            = 0;
    unsigned int offset = 0;
    
    chr = *(*p)++;
    switch (chr) {
      case 0x00:
        return;
      case 0x01: case 0x02:
        offset = Read3Byte(p);
        Seek(p, offset);
        *buf = (const char *)(*p);
        return;
      default:
        *buf = (const char *)(*p - 1);
    }
  }
  
  void CZParser::Seek(unsigned char **p, unsigned int offset, int nSeekType) {
    if (nSeekType == IPHTTPD_SEEK_SET) {
      *p = data_begin_prt_ + offset;
    } else if (nSeekType == IPHTTPD_SEEK_CUR) {
      *p += offset;
    }
  }
  
  unsigned int CZParser::Read4Byte(unsigned char **p) {
    unsigned int val = 0;
    val |= *(*p)++;
    val |= *(*p)++ << 8;
    val |= *(*p)++ << 16;
    val |= *(*p)++ << 24;
    return val;
  }
  
  unsigned int CZParser::Read3Byte(unsigned char **p) {
    unsigned int val = 0;
    val |= *(*p)++;
    val |= *(*p)++ << 8;
    val |= *(*p)++ << 16;
    return val;
  }
  
  unsigned int CZParser::IP2Long(const char *ip_str, unsigned int len) {
    IPTokenizer stToken(ip_str, len);
    return stToken.GetLongVal();
  }
  
  bool CZParser::GetLocation(const char *ip_str, unsigned int len,
                             IPEntry &ip_entry) {
    if (ip_str == NULL || len == 0) { return false; }
    
    return GetLocation(IP2Long(ip_str, len), ip_entry);
  }
  
  bool CZParser::GetLocation(unsigned int ip, IPEntry &ip_entry) {
    if(data_begin_prt_ == NULL) return false;
    
    unsigned char *cur_prt = data_begin_prt_;  
    unsigned int nLow     = 0;
    unsigned int nHigh    = total_count_;
    
    unsigned int nFindIP  = last_ip_;
    unsigned int begin_ip = 0;
    unsigned int end_ip   = 0;
    
    unsigned int tmp_offset = 0;
    size_t          tmp_len = 0;
    
    while (nLow <= nHigh) {
      unsigned int nMiddle = (nLow + nHigh) / 2;
      Seek(&cur_prt, first_ip_ + nMiddle * 7);
      begin_ip = Read4Byte(&cur_prt);
      
      if (ip < begin_ip) {
        nHigh = nMiddle - 1;
      } else {
        tmp_offset = Read3Byte(&cur_prt);
        Seek(&cur_prt, tmp_offset);
        end_ip = Read4Byte(&cur_prt);
        if (ip > end_ip) {
          nLow = nMiddle + 1;
        } else {
          nFindIP = first_ip_ + nMiddle * 7;
          break;
        }
      }
    }
    
    Seek(&cur_prt, nFindIP);
    ip_entry.begin_ip   = Read4Byte(&cur_prt);
    unsigned int offset = Read3Byte(&cur_prt);
    Seek(&cur_prt, offset);  
    ip_entry.end_ip     = Read4Byte(&cur_prt);
    
    char  chr = 0;
    chr = *cur_prt++;
    
    switch(chr) {
      case 0x01: // 国家和区域信息全部重定向
        offset = Read3Byte(&cur_prt);
        Seek(&cur_prt, offset);
        chr = *cur_prt++;
        switch(chr) {
          case 0x02:
            tmp_offset = Read3Byte(&cur_prt);
            Seek(&cur_prt, tmp_offset);
            ip_entry.country = (const char*)cur_prt;
            Seek(&cur_prt, offset + 4);
            GetArea(&cur_prt, &ip_entry.area);
            break;
          default:
            ip_entry.country = (const char*)(cur_prt - 1);
            tmp_len = strlen((const char*)cur_prt) + 1;
            Seek(&cur_prt, (unsigned int)tmp_len, IPHTTPD_SEEK_CUR);
            GetArea(&cur_prt, &ip_entry.area);
        }
        break;
      case 0x02:
        tmp_offset = Read3Byte(&cur_prt);
        Seek(&cur_prt, tmp_offset);
        ip_entry.country = (const char*)cur_prt;
        Seek(&cur_prt, offset + 8);
        GetArea(&cur_prt, &ip_entry.area);
        break;
      default:
        ip_entry.country = (const char*)(cur_prt - 1);
        tmp_len = strlen((const char*)cur_prt) + 1;
        Seek(&cur_prt, (unsigned int)tmp_len, IPHTTPD_SEEK_CUR);
        GetArea(&cur_prt, &ip_entry.area);
    }
    
    if(strcasecmp(ip_entry.country, " CZ88.NET") == 0) {
      ip_entry.country = "unkown";
    }
    if(strcasecmp(ip_entry.area, " CZ88.NET") == 0) {
      ip_entry.area = "unkown";
    }
    
    return true;
  }

} // /namespace
